package net.sourceforge.gomoku

import java.io.{StringReader, BufferedReader}
import collection.mutable
import annotation.tailrec


case class IdTrack(id:Int)

case class MaterializedPattern( pat: Pattern, shift: Cell ){
  val ruinSet = (for( ce <- pat.empty ) yield ce+shift).toSet
  def ruinedBy(c:Cell)={
    ruinSet contains c
  }
}

case class PossiblePattern( pat: Pattern, missing: Set[Cell], shift: Cell ){
  def shiftedMissing = missing map (_ + shift)
  def shiftedKillPoint = pat.killPoint + shift
}

case class Pattern( marks: Set[Cell], empty: Set[Cell], killPoint: Cell ){
  assert( empty contains killPoint )

  var idTrack : IdTrack = null

  def allSymetries(): Set[Pattern] = {
    val ret = Cell.combinedSymmOps map { func =>
      Pattern( marks map func, empty map func, func(killPoint)).zeroShift } toSet;
    for( p <- ret ){
      p.idTrack = idTrack
    }
    ret
  }

  def minXY(cells: Iterable[Cell]):Cell={
    Cell( cells.map( _.x ).min, cells.map( _.y ).min )
  }

  def maxXY(cells: Iterable[Cell]):Cell={
    Cell( cells.map( _.x ).max, cells.map( _.y ).max )
  }

  def minXY : Cell = minXY( marks ++ empty )

  def maxXY : Cell = maxXY( marks ++ empty )

  def zeroShift : Pattern = {
    val mins = minXY
    def shifter = (p:Cell) => Cell(p.x-mins.x, p.y-mins.y)
    Pattern( marks map shifter, empty map shifter, shifter(killPoint) )
  }

  def continuous():Boolean = {
    val nearCells = for( x <- -1 to 1; y <- -1 to 1 ) yield Cell(x,y)

    @tailrec
    def enlarge(cells:Set[Cell]):Set[Cell]={
      val nextLayer = for( c <- cells; n <- nearCells; cn = c + n;
           if (marks contains cn) || (empty contains cn) ) yield cn
      val nextSet = nextLayer.toSet
      if( nextSet.size==cells.size ){
        cells
      }else{
        enlarge(nextSet)
      }
    }

    val shouldBeAll = enlarge(Set(marks.head))
    marks.subsetOf( shouldBeAll ) && empty.subsetOf(shouldBeAll)
  }

  /**
   * If this pattern has extra unnecessary elements, and p is a better pattern
   * That is, if this is just a fat version of the true core pattern
   */
  def contains(p:Pattern):Boolean={
    minXY match {
      case Cell(0,0) =>
        if( marks.size >= p.marks.size && empty.size >= p.empty.size ){
          val maxMy = maxXY
          var found=false
          for( s <- p.allSymetries ){
            val maxs = s.maxXY
            for( x <- 0 to (maxMy.x - maxs.x) ; y <- 0 to (maxMy.y - maxs.y) ){
              if( s.marks.forall( c => marks.contains(c + Cell(x,y)) )
                && s.empty.forall( c => empty.contains(c + Cell(x,y)) ) ){
                found=true
              }
            }
          }
          found
        }else{
          false
        }
      case _ => (zeroShift contains p)
    }
  }

  def printView : String = {
    minXY match {
      case Cell(0,0) =>
        val maxs = maxXY
        val b = new StringBuilder
        for( y <- 0 to maxs.y ){
          for( x <- 0 to maxs.x ){
            val c = Cell(x,y)
            if( c==killPoint ){
              b.append('*')
            }else if( marks.contains(c) ){
              b.append('X')
            }else if( empty.contains(c) ){
              b.append('e')
            }else{
              b.append(".");
            }
          }
          b.append('\n')
        }
        b.toString
      case _ => zeroShift.printView
    }
  }
}

object Pattern{
  def patternFromPrintView(s:String):Pattern={
    val marks = new mutable.HashSet[Cell]
    val empty = new mutable.HashSet[Cell]
    var killPoint:Cell=null

    val br = new BufferedReader(new StringReader(s))
    var row=0
    var stop=false
    while( !stop ){
      val line=br.readLine()
      if( line==null ){
        stop=true
      }else{
        val l = line.trim
        if( l!="" ){
          for( i <- 0 until l.size ){
            if( l(i)=='*' ){
              killPoint = Cell(i,row)
              empty += killPoint
            }
            if( l(i)=='e' ){
              empty += Cell(i,row)
            }
            if( l(i)=='X' ){
              marks += Cell(i,row)
            }
          }
          row += 1
        }
      }
    }
    Pattern(marks.toSet, empty.toSet, killPoint)
  }
}
